1   /*
2    * Javassist, a Java-bytecode translator toolkit.
3    * Copyright (C) 1999- Shigeru Chiba. All Rights Reserved.
4    *
5    * The contents of this file are subject to the Mozilla Public License Version
6    * 1.1 (the "License"); you may not use this file except in compliance with
7    * the License.  Alternatively, the contents of this file may be used under
8    * the terms of the GNU Lesser General Public License Version 2.1 or later,
9    * or the Apache License Version 2.0.
10   *
11   * Software distributed under the License is distributed on an "AS IS" basis,
12   * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
13   * for the specific language governing rights and limitations under the
14   * License.
15   */
16  
17  package scouter.javassist;
18  
19  import java.io.File;
20  import java.io.FileInputStream;
21  import java.io.FileNotFoundException;
22  import java.io.FilenameFilter;
23  import java.io.IOException;
24  import java.io.InputStream;
25  import java.io.OutputStream;
26  import java.net.MalformedURLException;
27  import java.net.URL;
28  import java.util.jar.JarEntry;
29  import java.util.jar.JarFile;
30  
31  import scouter.javassist.CannotCompileException;
32  import scouter.javassist.ClassClassPath;
33  import scouter.javassist.ClassPath;
34  import scouter.javassist.NotFoundException;
35  
36  
37  final class ClassPathList {
38      ClassPathList next;
39      ClassPath path;
40  
41      ClassPathList(ClassPath p, ClassPathList n) {
42          next = n;
43          path = p;
44      }
45  }
46  
47  final class DirClassPath implements ClassPath {
48      String directory;
49  
50      DirClassPath(String dirName) {
51          directory = dirName;
52      }
53  
54      public InputStream openClassfile(String classname) {
55          try {
56              char sep = File.separatorChar;
57              String filename = directory + sep
58                  + classname.replace('.', sep) + ".class";
59              return new FileInputStream(filename.toString());
60          }
61          catch (FileNotFoundException e) {}
62          catch (SecurityException e) {}
63          return null;
64      }
65  
66      public URL find(String classname) {
67          char sep = File.separatorChar;
68          String filename = directory + sep
69              + classname.replace('.', sep) + ".class";
70          File f = new File(filename);
71          if (f.exists())
72              try {
73                  return f.getCanonicalFile().toURI().toURL();
74              }
75              catch (MalformedURLException e) {}
76              catch (IOException e) {}
77  
78          return null;
79      }
80  
81      public void close() {}
82  
83      public String toString() {
84          return directory;
85      }
86  }
87  
88  final class JarDirClassPath implements ClassPath {
89      JarClassPath[] jars;
90  
91      JarDirClassPath(String dirName) throws NotFoundException {
92          File[] files = new File(dirName).listFiles(new FilenameFilter() {
93              public boolean accept(File dir, String name) {
94                  name = name.toLowerCase();
95                  return name.endsWith(".jar") || name.endsWith(".zip");
96              }
97          });
98  
99          if (files != null) {
100             jars = new JarClassPath[files.length];
101             for (int i = 0; i < files.length; i++)
102                 jars[i] = new JarClassPath(files[i].getPath());
103         }
104     }
105 
106     public InputStream openClassfile(String classname) throws NotFoundException {
107         if (jars != null)
108             for (int i = 0; i < jars.length; i++) {
109                 InputStream is = jars[i].openClassfile(classname);
110                 if (is != null)
111                     return is;
112             }
113 
114         return null;    // not found
115     }
116 
117     public URL find(String classname) {
118         if (jars != null)
119             for (int i = 0; i < jars.length; i++) {
120                 URL url = jars[i].find(classname);
121                 if (url != null)
122                     return url;
123             }
124 
125         return null;    // not found
126     }
127 
128     public void close() {
129         if (jars != null)
130             for (int i = 0; i < jars.length; i++)
131                 jars[i].close();
132     }
133 }
134 
135 final class JarClassPath implements ClassPath {
136     JarFile jarfile;
137     String jarfileURL;
138 
139     JarClassPath(String pathname) throws NotFoundException {
140         try {
141             jarfile = new JarFile(pathname);
142             jarfileURL = new File(pathname).getCanonicalFile()
143                                            .toURI().toURL().toString();
144             return;
145         }
146         catch (IOException e) {}
147         throw new NotFoundException(pathname);
148     }
149 
150     public InputStream openClassfile(String classname)
151         throws NotFoundException
152     {
153         try {
154             String jarname = classname.replace('.', '/') + ".class";
155             JarEntry je = jarfile.getJarEntry(jarname);
156             if (je != null)
157                 return jarfile.getInputStream(je);
158             else
159                 return null;    // not found
160         }
161         catch (IOException e) {}
162         throw new NotFoundException("broken jar file?: "
163                                     + jarfile.getName());
164     }
165 
166     public URL find(String classname) {
167         String jarname = classname.replace('.', '/') + ".class";
168         JarEntry je = jarfile.getJarEntry(jarname);
169         if (je != null)
170             try {
171                 return new URL("jar:" + jarfileURL + "!/" + jarname);
172             }
173             catch (MalformedURLException e) {}
174 
175         return null;            // not found
176     }
177 
178     public void close() {
179         try {
180             jarfile.close();
181             jarfile = null;
182         }
183         catch (IOException e) {}
184     }
185 
186     public String toString() {
187         return jarfile == null ? "<null>" : jarfile.toString();
188     }
189 }
190 
191 final class ClassPoolTail {
192     protected ClassPathList pathList;
193 
194     public ClassPoolTail() {
195         pathList = null;
196     }
197 
198     public String toString() {
199         StringBuffer buf = new StringBuffer();
200         buf.append("[class path: ");
201         ClassPathList list = pathList;
202         while (list != null) {
203             buf.append(list.path.toString());
204             buf.append(File.pathSeparatorChar);
205             list = list.next;
206         }
207 
208         buf.append(']');
209         return buf.toString();
210     }
211 
212     public synchronized ClassPath insertClassPath(ClassPath cp) {
213         pathList = new ClassPathList(cp, pathList);
214         return cp;
215     }
216 
217     public synchronized ClassPath appendClassPath(ClassPath cp) {
218         ClassPathList tail = new ClassPathList(cp, null);
219         ClassPathList list = pathList;
220         if (list == null)
221             pathList = tail;
222         else {
223             while (list.next != null)
224                 list = list.next;
225 
226             list.next = tail;
227         }
228 
229         return cp;
230     }
231 
232     public synchronized void removeClassPath(ClassPath cp) {
233         ClassPathList list = pathList;
234         if (list != null)
235             if (list.path == cp)
236                 pathList = list.next;
237             else {
238                 while (list.next != null)
239                     if (list.next.path == cp)
240                         list.next = list.next.next;
241                     else
242                         list = list.next;
243             }
244 
245         cp.close();
246     }
247 
248     public ClassPath appendSystemPath() {
249         return appendClassPath(new ClassClassPath());
250     }
251 
252     public ClassPath insertClassPath(String pathname)
253         throws NotFoundException
254     {
255         return insertClassPath(makePathObject(pathname));
256     }
257 
258     public ClassPath appendClassPath(String pathname)
259         throws NotFoundException
260     {
261         return appendClassPath(makePathObject(pathname));
262     }
263 
264     private static ClassPath makePathObject(String pathname)
265         throws NotFoundException
266     {
267         String lower = pathname.toLowerCase();
268         if (lower.endsWith(".jar") || lower.endsWith(".zip"))
269             return new JarClassPath(pathname);
270 
271         int len = pathname.length();
272         if (len > 2 && pathname.charAt(len - 1) == '*'
273             && (pathname.charAt(len - 2) == '/'
274                 || pathname.charAt(len - 2) == File.separatorChar)) {
275             String dir = pathname.substring(0, len - 2);
276             return new JarDirClassPath(dir);
277         }
278 
279         return new DirClassPath(pathname);
280     }
281 
282     /**
283      * This method does not close the output stream.
284      */
285     void writeClassfile(String classname, OutputStream out)
286         throws NotFoundException, IOException, CannotCompileException
287     {
288         InputStream fin = openClassfile(classname);
289         if (fin == null)
290             throw new NotFoundException(classname);
291 
292         try {
293             copyStream(fin, out);
294         }
295         finally {
296             fin.close();
297         }
298     }
299 
300     /*
301     -- faster version --
302     void checkClassName(String classname) throws NotFoundException {
303         if (find(classname) == null)
304             throw new NotFoundException(classname);
305     }
306 
307     -- slower version --
308 
309     void checkClassName(String classname) throws NotFoundException {
310         InputStream fin = openClassfile(classname);
311         try {
312             fin.close();
313         }
314         catch (IOException e) {}
315     }
316     */
317 
318 
319     /**
320      * Opens the class file for the class specified by
321      * <code>classname</code>.
322      *
323      * @param classname             a fully-qualified class name
324      * @return null                 if the file has not been found.
325      * @throws NotFoundException    if any error is reported by ClassPath.
326      */
327     InputStream openClassfile(String classname)
328         throws NotFoundException
329     {
330         ClassPathList list = pathList;
331         InputStream ins = null;
332         NotFoundException error = null;
333         while (list != null) {
334             try {
335                 ins = list.path.openClassfile(classname);
336             }
337             catch (NotFoundException e) {
338                 if (error == null)
339                     error = e;
340             }
341 
342             if (ins == null)
343                 list = list.next;
344             else
345                 return ins;
346         }
347 
348         if (error != null)
349             throw error;
350         else
351             return null;    // not found
352     }
353 
354     /**
355      * Searches the class path to obtain the URL of the class file
356      * specified by classname.  It is also used to determine whether
357      * the class file exists.
358      *
359      * @param classname     a fully-qualified class name.
360      * @return null if the class file could not be found.
361      */
362     public URL find(String classname) {
363         ClassPathList list = pathList;
364         URL url = null;
365         while (list != null) {
366             url = list.path.find(classname);
367             if (url == null)
368                 list = list.next;
369             else
370                 return url;
371         }
372 
373         return null;
374     }
375 
376     /**
377      * Reads from an input stream until it reaches the end.
378      *
379      * @return          the contents of that input stream
380      */
381     public static byte[] readStream(InputStream fin) throws IOException {
382         byte[][] bufs = new byte[8][];
383         int bufsize = 4096;
384 
385         for (int i = 0; i < 8; ++i) {
386             bufs[i] = new byte[bufsize];
387             int size = 0;
388             int len = 0;
389             do {
390                 len = fin.read(bufs[i], size, bufsize - size);
391                 if (len >= 0)
392                     size += len;
393                 else {
394                     byte[] result = new byte[bufsize - 4096 + size];
395                     int s = 0;
396                     for (int j = 0; j < i; ++j) {
397                         System.arraycopy(bufs[j], 0, result, s, s + 4096);
398                         s = s + s + 4096;
399                     }
400 
401                     System.arraycopy(bufs[i], 0, result, s, size);
402                     return result;
403                 }
404             } while (size < bufsize);
405             bufsize *= 2;
406         }
407 
408         throw new IOException("too much data");
409     }
410 
411     /**
412      * Reads from an input stream and write to an output stream
413      * until it reaches the end.  This method does not close the
414      * streams.
415      */
416     public static void copyStream(InputStream fin, OutputStream fout)
417         throws IOException
418     {
419         int bufsize = 4096;
420         byte[] buf = null;
421         for (int i = 0; i < 64; ++i) {
422             if (i < 8) {
423                 bufsize *= 2;
424                 buf = new byte[bufsize];
425             }
426             int size = 0;
427             int len = 0;
428             do {
429                 len = fin.read(buf, size, bufsize - size);
430                 if (len >= 0)
431                     size += len;
432                 else {
433                     fout.write(buf, 0, size);
434                     return;
435                 }
436             } while (size < bufsize);
437             fout.write(buf);
438         }
439 
440         throw new IOException("too much data");
441     }
442 }